/*
 * @(#)FileDumper.java	2008/08/24
 *
 * Copyright 2008 Web & Zen Multimedia, All rights reserved.
 * Use is subject to license terms.
 */
package it.wzm.lightweightlog;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;
import java.nio.channels.*;


/**
 * <p>This class should be subclassed to create other file dumper, for example xml,
 *  xls...etc.<br>
 *  As it is, it just creates a text file.
 * </p>
 * <p>
 *  In addition of the property needed by Dumper superclass, this is a listing of all the other properties, all of them must be prefixed
 *  following the rule of {@link Dumper}:
 * <ul>
 *  <li>url = where the file is saved</li>
 *  <li>rotation = the type of rotation, only SIZE is allowed in this release</li>
 *  <li>size = the size when the file need to be rotated, 10000 means 10Kb</li>
 * </ul>
 * </p>
 * <p>
 *  This class use a buffered writer to dump Entry to the file. When the data are
 *  written the class first check the file existance, if not exists (at startup,
 *  or when it is rotated) then it is created.<br>
 *  The checking of the rotation happens after the dumping of all entry is done.
 * </p>
 */
public class FileDumper extends Dumper {

    /**
     * needed for serialization
     */
    static final long serialVersionUID = 20080618L;

    /**
     * rotation type for a file dumper
     */
    public enum Rotation {

        SIZE((short) 0, "size"),
        DATE((short) 1, "date");
        short type;  // for use in switch or whatever...type
        String name; // human readable name

        Rotation(short type, String name) {
            this.type = type;
            this.name = name.toUpperCase();
        }
        // getter for the variable above
        short getType() {
            return this.type;
        }

        String getName() {
            return this.name;
        }
    }

    /**
     * type of rotation based on date
     */
    public enum DateRule {
        HOURLY,
        DAILY,
        WEEKLY,
        MONTHLY,
        YEARLY,
    }
    /**
     * The main stream for writing to file
     */
    private BufferedWriter bw;
    /**
     * the path of the file
     */
    private String url;
    /**
     * type of rotation
     */
    private Rotation rotation = Rotation.SIZE;
    /**
     * the size of the rotation
     */
    private long size;
    /**
     * the file itself
     */
    private File f;
    /**
     * The channel...
     */
    private FileChannel ch;
    /**
     * The file stream associated with this File
     */
	private FileOutputStream fos;
	/**
	 * our lock to file
	 */
	private FileLock lock;


    /**
     * Construct a FileDumper with a given set of properties
     * @param p Properties
     */
    public FileDumper(Properties p) {
        // super constructor...
        super(p);
    }
    
    /**
     * Adding our custom property
     */
    @Override
    protected void postinit() {
        try {
            // where this file is, we also 
            // perform a check if we can write the file...
            this.url = this.get("url");
            this.f = new File(this.url);
            //if (!f.canWrite()) {
            //    throw new Exception("Cannot write " + this.url);
             //}

            // determine rotation type with default to size if null
            if (this.get("rotation") != null) {
                rotation = Rotation.valueOf(
                        (this.get("rotation")).toUpperCase());

                // loading different property depending on rotation type
                switch (rotation) {
                    // rotation by size
                    case SIZE:
                        // maximum size before rotating
                        if (this.get("size") != null) {
                            this.size = Long.parseLong(this.get("size"));
                        } else {
                            this.size = DEFAULT_SIZE;
                        }
                        break;
                    /* To be implemented
                    case DATE:
                    // check the date...because rotation by date is not
                    // default, user has to specify all the parameter
                    // that's why we are not catching and setting any errors
                    break;
                     */
                }
                
            } else {
                // setting default rotation to size
                // with dfault rotation size
                this.rotation = Rotation.SIZE;
                this.size = DEFAULT_SIZE;
            }            
            
        //} catch (DumperException e) {
        //	throw new ObjectInitializationException(e);
        } catch (Exception e) {
            throw new DumperInitializationException(e);
        }

    }
    
    /**
     * Tell wether or not the File is open
     */
    @Override
    public boolean isOpen() {
    	// basic check first...
    	if (ch == null)
    		return false;
    	
    	return ch.isOpen();
    }
    
    /**
     * Open this file 
     * @throws DumperException
     */
    @Override
    public void open() throws DumperException {
        try {
            //ch = new RandomAccessFile(f, "rw").getChannel();
            fos = new FileOutputStream(f,true);
            ch = fos.getChannel();
            //bw = new BufferedWriter(Channels.newWriter(ch, Constant.DEFAULT_ENCODING));
            synchronized(this){
            	bw = new BufferedWriter(Channels.newWriter(ch, Constant.DEFAULT_ENCODING));
            }
            // acquire lock
            lock = ch.tryLock();

        } catch (IOException e) {
            throw new DumperException(e);
        }
    }

    /**
     * 
     * @param e Anything extending Entry
     * @throws DumperException
     */
    @Override
    public <E extends Entry> void dump(E e) throws DumperException {
        try {
            // dumping entry to file
            bw.append(e.getLine());
            bw.flush();

            // check if we need to rotate
        	// if yes...then the step are:
        	// close-->rotate-->open
            //if (rotate) {
            if (ch.size() > this.size) {
            	this.close();
                this.rotateLog();
                this.open();
            }
        } catch (Exception ex) {ex.printStackTrace();
            throw new DumperException(ex);
        }
    }

    /**
     * Flush and close the BufferedWriter used to write to file
     */
    @Override
    public void close() throws DumperException {
        try {
            // basic closing...
            // but we first flush it...
            bw.flush();
            bw.close();

        } catch (IOException e) {
            throw new DumperException(e);
        }
    }

    /**
     * Rotate our log file
     * @throws IOException If something went wrong rotating file
     */
    protected synchronized final void rotateLog() throws IOException, DumperException {
        // don't be scared by this, final result will be: filename.log20080610180512
        String oldLog = url + new SimpleDateFormat("yyyyMMddhhmmss").format(new Date());

        // rename and check if it went fine        
        if (!f.renameTo(new File(oldLog))) {
            throw new IOException("Couldn't do rotation by size");
        }
        //Log.serviceLog(t.getName());

        //this.isOpen=false;
        // if keep alive true we need to reopen the file
        // otherwise this is done normally in postprepare()
        //if (this.isKeepAlive()) {
        //	this.open();
        //}

        //notifyAll();
    }
    
    /**
     * Killing this dumper
     */
    @Override
    public void kill() {    	
    	try {
    		// nothing more than closing...
    		if (this.isOpen())
    			this.close();
		} catch (DumperException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			// then free other resources...
			try {
				if (fos != null)
					fos.close();
			} catch (IOException e) {
				// doing nothing...
			}
    		fos = null;
		}    	
    }
}




